
import chisel3._
import chisel3.util._
import chiseltest._
import org.scalatest.{Matchers, FlatSpec}
import scala.util.Random

import comlib.counter._

class AdderImplSpec extends FlatSpec with ChiselScalatestTester with Matchers {

  it should "支持简单加+减操作" in {
    test( new AdderImpl(15.U) ){ dut=>
      dut.in0.inc.poke( true.B )
      dut.in0.value.poke( 12.U )
      dut.in0.overflow.poke( false.B )
      dut.in0.underflow.poke( false.B )

      dut.in1.inc.poke( false.B )
      dut.in1.value.poke( 4.U )
      dut.in1.overflow.poke( false.B )
      dut.in1.underflow.poke( false.B )

      dut.out.value.expect( 8.U )
      dut.out.overflow.expect( false.B )
      dut.out.underflow.expect( false.B )
    }
  }

  it should "支持简单加+加操作" in {
    test( new AdderImpl(15.U) ){ dut=>
      dut.in0.inc.poke( true.B )
      dut.in0.value.poke( 8.U )
      dut.in0.overflow.poke( false.B )
      dut.in0.underflow.poke( false.B )

      dut.in1.inc.poke( true.B )
      dut.in1.value.poke( 4.U )
      dut.in1.overflow.poke( false.B )
      dut.in1.underflow.poke( false.B )

      dut.out.value.expect( 12.U )
      dut.out.overflow.expect( false.B )
      dut.out.underflow.expect( false.B )
    }
  }

  it should "支持下溢判断" in {
    test( new AdderImpl(15.U) ){ dut=>
      dut.in0.inc.poke( false.B )
      dut.in0.value.poke( 8.U )
      dut.in0.overflow.poke( false.B )
      dut.in0.underflow.poke( false.B )

      dut.in1.inc.poke( false.B )
      dut.in1.value.poke( 8.U )
      dut.in1.overflow.poke( false.B )
      dut.in1.underflow.poke( false.B )

      dut.out.value.expect( 0.U )
      dut.out.overflow.expect( false.B )
      dut.out.underflow.expect( true.B )
    }
  }
}

class CountImplSpec extends FlatSpec with ChiselScalatestTester with Matchers {

  val rnd = new Random

  it should "支持循环inc操作" in {
    test( new CountImpl(15.U, 15.U, 1, false) ){ dut =>

      val init = rnd.nextInt(16)
      val ival = rnd.nextInt(16)

      dut.io.load.poke(init.U(4.W))
      dut.io.mode.poke(0.U)

      dut.io.cmds(0).cmd.poke(CNT_CMD.INC)
      dut.io.cmds(0).value.poke( ival.U )

      dut.io.force.poke(false.B)
      dut.io.forceValue.poke(0.U)
      dut.io.min.poke(0.U)
      dut.io.max.poke(15.U)

      dut.io.value.expect( ((init+ival)%16).U, f"init=${init} ival=${ival} " ) 
    }
  }

  it should "支持清零dec操作" in {
    test( new CountImpl(15.U, 15.U, 1, false) ){ dut =>

      val init = rnd.nextInt(16)
      val ival = rnd.nextInt(16)

      dut.io.load.poke( init.U )
      dut.io.mode.poke( CNT_MODE.LOOP )
      dut.io.cmds(0).cmd.poke( CNT_CMD.INC )
      dut.io.cmds(0).value.poke( ival.U )
      dut.io.clear.poke( true.B )
      dut.io.force.poke(false.B)
      dut.io.forceValue.poke(0.U)
      dut.io.min.poke(0.U)
      dut.io.max.poke(15.U)

      dut.io.value.expect( ival.U, f"init=${init} ival=${ival} " ) 
    }
  }

  it should "支持循环固定inc操作" in {
    test( new CountImpl(15.U, 15.U, 2, false) ){dut =>
      dut.io.mode.poke( CNT_MODE.LOOP )
      dut.io.min.poke( 0.U )
      dut.io.max.poke( 15.U )
      dut.io.force.poke( false.B )
      dut.io.forceValue.poke( 0.U )
      dut.io.clear.poke( false.B )

      dut.io.load.poke( 0.U )
      dut.io.cmds(0).cmd.poke( CNT_CMD.INC )
      dut.io.cmds(0).value.poke( 8.U )
      dut.io.cmds(1).cmd.poke( CNT_CMD.DEC )
      dut.io.cmds(1).value.poke( 3.U )

      dut.io.value.expect( 5.U )
      dut.io.overflow.expect( false.B )
      dut.io.underflow.expect( false.B )
      dut.io.overscope.expect( false.B )
      
    }
  }

  it should "支持循环随机inc&dec操作" in {
    val rndBit = 6
    val rndSize = Math.pow(2, rndBit).toInt

    test( new CountImpl(rndSize.U, rndSize.U, 2, false) ){ dut =>

      for( i <- 0 until 100 ){
        val maxT:Int  = rnd.nextInt(rndSize-1)+1
        val minT:Int  = rnd.nextInt(maxT)
        val init = rnd.nextInt(maxT+1-minT) + minT
        val ival = rnd.nextInt(maxT-minT+1) + minT
        val dval = rnd.nextInt(maxT-minT+1) + minT

        dut.io.load.poke( init.U )
        dut.io.mode.poke( CNT_MODE.LOOP )
        dut.io.cmds(0).cmd.poke( CNT_CMD.INC )
        dut.io.cmds(0).value.poke( ival.U )
        dut.io.cmds(1).cmd.poke( CNT_CMD.DEC )
        dut.io.cmds(1).value.poke( dval.U )
        dut.io.force.poke(false.B)
        dut.io.forceValue.poke(0.U)
        dut.io.min.poke( minT.U )
        dut.io.max.poke( maxT.U )
        dut.io.clear.poke( false.B )

        val (of, uf, rst) = {
          val r1 = init + ival - dval
          if( r1 > maxT ) (true, false, (r1-maxT)%(maxT-minT+1)+minT-1)
          else if( r1 < minT ) (false, true, maxT-(minT-r1)%(maxT-minT+1)+1)
          else (false, false, r1)
        }

        dut.io.value.expect( rst.U, f"init=${init} ival=${ival} dval=${dval} max=${maxT} min=${minT}" )
        dut.io.overflow.expect( of.B, f"init=${init} ival=${ival} dval=${dval} max=${maxT} min=${minT}" )
        dut.io.underflow.expect( uf.B, f"init=${init} ival=${ival} dval=${dval} max=${maxT} min=${minT}" )

      }
 
    }
  }

  it should "支持饱和随机inc&dec操作" in {
    val rndBit = 6
    val rndSize = Math.pow(2, rndBit).toInt

    test( new CountImpl(rndSize.U, rndSize.U, 2, false) ){ dut =>

      for( i <- 0 until 100 ){
        val maxT:Int  = rnd.nextInt(rndSize-1)+1
        val minT:Int  = rnd.nextInt(maxT)
        val init = rnd.nextInt(maxT+1-minT) + minT
        val ival = rnd.nextInt(maxT-minT+1) + minT
        val dval = rnd.nextInt(maxT-minT+1) + minT

        dut.io.load.poke(init.U)
        dut.io.mode.poke( CNT_MODE.SAF )
        dut.io.cmds(0).cmd.poke( CNT_CMD.INC )
        dut.io.cmds(0).value.poke( ival.U )
        dut.io.cmds(1).cmd.poke( CNT_CMD.DEC )
        dut.io.cmds(1).value.poke( dval.U )
        dut.io.force.poke(false.B)
        dut.io.forceValue.poke(0.U)
        dut.io.min.poke( minT.U )
        dut.io.max.poke( maxT.U )
        dut.io.clear.poke( false.B )

        val (of, uf, rst) = {
          val r1 = init + ival - dval
          if( r1 > maxT ) (true, false, maxT)
          else if( r1 < minT ) (false, true, minT)
          else (false, false, r1)
        }
        dut.io.value.expect( rst.U, f"init=${init} ival=${ival} dval=${dval} max=${maxT} min=${minT}" )
        dut.io.overflow.expect( of.B, f"init=${init} ival=${ival} dval=${dval} max=${maxT} min=${minT}" )
        dut.io.underflow.expect( uf.B, f"init=${init} ival=${ival} dval=${dval} max=${maxT} min=${minT}" )
      }

    }
  }

}

class CountSpec extends FlatSpec with ChiselScalatestTester with Matchers {

  behavior of "Count"

  val rnd = new Random

  class MyCount extends Module{
    val io = IO( new Bundle{
      val load = Input(UInt(7.W))
      val inc = Input(UInt(2.W))
      val ival = Input(UInt(7.W))
      val dec = Input(UInt(2.W))
      val dval = Input(UInt(7.W))
      val value = Output(UInt(7.W))
    } )

    val cnter = Count[UInt,UInt](io.load, 31.U, 90.U)
    // cnter.io.load := io.load
    cnter.io.cmds(0).cmd := io.inc
    cnter.io.cmds(0).value := io.ival
    cnter.io.cmds(1).cmd := io.dec
    cnter.io.cmds(1).value := io.dval
    io.value := cnter.io.value 
  }

  it should "支持设置上下边界计数器" ignore {

    test( new MyCount ){ dut =>

      for( i <- 0 until 100 ){
        val init = rnd.nextInt(60) + 31
        val ival = rnd.nextInt(60)
        val dval = rnd.nextInt(60)

        dut.io.load.poke(init.U)
        dut.io.inc.poke( CNT_CMD.INC )
        dut.io.ival.poke( ival.U )
        dut.io.dec.poke( CNT_CMD.DEC )
        dut.io.dval.poke( dval.U )

         val (of, uf, rst) = {
          val r1 = init + ival - dval
          if( r1 > 90 ) (true, false, (r1-90)%60+30)
          else if( r1 < 31 ) (false, true, 90-(31-r1)%60+1)
          else (false, false, r1)
        }

        dut.io.value.expect( rst.U, f"init=${init} ival=${ival} dval=${dval}") 
      }
 
    }
  }

}